package moro;

/**
 * Title:        The MObile RObot Simulation Environment
 * Description:
 * Copyright:    Copyright (c) 2001
 * Company:      Universit� di Bergamo
 * @author Davide Brugali
 * @version 1.0
 */
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Polygon;
import java.io.PrintWriter;
import java.util.ArrayList;

public abstract class Device implements Runnable {

    String name;                  // the name of this device
    Environment environment;          // a reference to the environment
    Polygon shape = new Polygon();        // the device's shape in local coords
    // a reference to the robot
    Robot robot;
    // the robot current position
    Position robotPos = new Position();
    // origin of the device reference frame with regards to the robot frame
    Position localPos;
    Color bgColor = Color.GRAY;
    Color fgColor = Color.blue;
    Color txtColor = Color.WHITE;
    protected Thread thread = null;
    protected long delay = 2;       // sensor scan speed
    protected boolean alive = false;   // Is the device started?
    protected boolean running = false;   // Is the device executing a command?
    protected Object lock = new Object();
    // arraylist met nog uit te voeren commandos
    protected ArrayList<String> commands = new ArrayList<String>();
    protected PrintWriter output = null;
    long seconds = 0;
    boolean toggleSwitch;

    // the constructor
    public Device(String name, Robot robot, Position local, Environment environment) {
        this.name = name;
        this.robot = robot;
        robot.readPosition(this.robotPos);
        this.localPos = local;
        this.environment = environment;
        // This will cause the device run() to be started in a separate thread.
        thread = new Thread(this);
    }

    /**
     * this method is invoked when the geometric shape of the device is defined
     * @param x  x-position
     * @param y  y-position
     */
    public void addPoint(int x, int y) {
        shape.addPoint(x, y);
    }

    /**
     * PIPE - IN
     * hier worden de commandos van de Controller naartoe geschreven
     * @param command command to be send to device
     * @return  true is succes
     */
    public boolean sendCommand(String command) {
        commands.add(command);
        synchronized (lock) {
            // Wake up another thread that is waiting for commands.
            lock.notify();
        }
        return true;
    }

    /**
     * PIPE - OUT
     * hier worden de resultaten weer naar de Controller gestuurd
     * @param data info voor Controller 
     */
    protected synchronized void writeOut(String data) {
        if (output != null) {
            // debug doeleinden, bekijk retourinfo naar de controller
            System.out.println(this.name + " output= " + data);
            output.println(data);
        } else {
            System.out.println(this.name + " output not initialized");
        }
    }

    /**
     *  start a device
     */
    public synchronized void start() {
        if (!alive) {
            alive = true;
            thread.start();
        }
    }

    /**
     *  stop a device
     */
    public synchronized void quit() {
        if (alive) {
            alive = false;
            // thread might be waiting for commands so give it an interrupt.
            thread.interrupt();
        }
    }

    /**
     *  hier worden de door de Controller gestuurde commandos verwerkt
     *  het eerste commando (index 0) wordt verwijderd en uitgevoerd, een soort stack dus.
     *  deze methode draait continu door tot einde programma! (alive=true) stop device
     */
    @Override
    public void run() {
        System.out.println("Device " + this.name + " started");
        do {
            try {
                if (running) {
                    // busy now, pause before the next step
                    synchronized (this) {
                        Thread.sleep(delay);
                    }
                } else if (commands.size() > 0) {
                    // extracts the the next command and executes it
                    String command = commands.remove(0);
                    // voer het commando daadwerkelijk uit (door het juiste device: P1, S1, L1)
                    //
                    // @@@ toon verstuurde commandos optioneel @@@
                    System.out.println("Device " + this.name + " executing: " + command);
                    executeCommand(command);
                } else {
                    // waits for a new command
                    synchronized (lock) {
                        // suspend this thread and wait to be notified about 
                        // a new command (in sendCommand()).
                        lock.wait();
                    }
                }

                // processes a new step
                // overridden: methode "nextStep" zit in de subklassen van Device
                nextStep();
            } catch (InterruptedException ie) {
                continue;
            }
        } while (alive);
    }

    /**
     * 
     * @param output set output for controller
     */
    public void setOutput(PrintWriter output) {
        this.output = output;
    }

    /**
     * 
     * @return name of device
     */
    public String getName() {
        return name;
    }
    
    public abstract int getRange(); 

    public abstract void paint(Graphics g);

    public abstract void executeCommand(String command);

    public abstract void nextStep();
    
}
